﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace Sworm
{
    public class SimpleQuery<TRoot>
    {
        private readonly Database db;
        private readonly SqlDialect dialect;
        private readonly MetadataBuilder metadata;
        private readonly TableData table;

        public SimpleQuery(Database db)
        {
            this.db = db;
            this.dialect = db.Dialect;
            this.metadata = db.MetadataBuilder;
            this.table = metadata.TableDataFor(typeof(TRoot));
        }

        private StringBuilder sqlBuilder = new StringBuilder();
        private ParamCollector paramCollector = new ParamCollector();

        public SimpleQuery<TRoot> Append(string sql)
        {
            this.sqlBuilder.AppendLine(sql);
            return this;
        }

        public SimpleQuery<TRoot> Append(string sql, params object[] args)
        {
            paramCollector.Collect(ref sql, args);
            this.sqlBuilder.AppendLine(sql);
            return this;
        }

        public SimpleQuery<TRoot> Select(string sql)
        {
            sqlBuilder.Append("select ").Append(sql).AppendLine();
            return this;
        }

        public SimpleQuery<TRoot> Select(params Expression<Func<TRoot, object>>[] columns)
        {
            var cols = columns.Select(c => table.RequireColumn(c));
            sqlBuilder.Append("select " + string.Join(", ", cols.Select(c => c.Name.Escaped)));
            return this;
        }

        public SimpleQuery<TRoot> SelectAllColumns()
        {
            sqlBuilder.Append("select");

            string comma = "";
            foreach (var column in table.Columns)
            {
                sqlBuilder.Append(comma).Append(column.Name.Escaped);
                comma = ", ";
            }

            sqlBuilder.AppendLine();
            return this;
        }

        public SimpleQuery<TRoot> From(string table)
        {
            sqlBuilder.AppendFormat("from {0}", dialect.EscapeTableName(table)).AppendLine();
            return this;
        }

        public SimpleQuery<TRoot> FromDefaultTable()
        {
            sqlBuilder.AppendFormat("from {0}", table.Name.Escaped);
            return this;
        }

        // TODO duplicated between JoinQuery and this
        private bool firstWhere = true;
        public SimpleQuery<TRoot> Where(string sql, params object[] args)
        {
            if (firstWhere)
            {
                this.sqlBuilder.Append(" where (");
                firstWhere = false;
            }
            else
            {
                this.sqlBuilder.Append(" and (");
            }

            Append(sql, args);
            this.sqlBuilder.Append(")");
            return this;
        }

        private bool firstOrderBy = true;
        public SimpleQuery<TRoot> OrderBy(string sql)
        {
            if (firstOrderBy)
            {
                this.sqlBuilder.Append(" order by ");
                firstOrderBy = false;
            }
            else
            {
                this.sqlBuilder.Append(", ");
            }
            return Append(sql);
        }

        private bool SqlIsEmpty { get { return this.sqlBuilder.Length == 0; } }

        public TRoot Single(object primaryKeyValue)
        {
            var pk = table.RequirePrimaryKey();

            if (SqlIsEmpty)
            {
                SelectAllColumns();
                FromDefaultTable();
            }

            Where(pk.Name.Escaped + " = @0", primaryKeyValue);

            return Single();
        }

        public TRoot Single()
        {
            return List().Single();
        }

        public List<TRoot> List()
        {
            List<TRoot> retval = new List<TRoot>();

            List<ColumnMappedAccessor> accessors = new List<ColumnMappedAccessor>();

            if (SqlIsEmpty)
            {
                SelectAllColumns().FromDefaultTable();
            }
            string sql = this.sqlBuilder.ToString();

            db.ExecuteReader(sql, paramCollector.Params, reader =>
            {
                var r = reader.Reader;

                foreach (var kvp in reader.columns)
                {
                    var x = new DbAccessor(r, kvp.Value);
                    var column = table.TryFindColumn(kvp.Key);
                    if (column != null)
                    {
                        accessors.Add(new ColumnMappedAccessor(x, column));
                    }
                }

                while (r.Read())
                {
                    var item = table.CreateInstance<TRoot>();
                    retval.Add(item);

                    foreach (var acc in accessors)
                    {
                        acc.column.SetDbValue(item, acc.accessor);
                    }
                }
            });

            return retval;
        }
    }
}
